package cn.com.zero.manager.auth.service.impl;

import cn.com.zero.api.auth.util.TokenUtil;
import cn.com.zero.api.auth.util.UserCacheKeyUtil;
import cn.com.zero.api.message.dubbo.IMessageService;
import cn.com.zero.api.auth.config.properties.TokenProperties;
import cn.com.zero.base.exception.ErrorCodeException;
import cn.com.zero.base.service.BaseService;
import cn.com.zero.base.util.RedisUtil;
import cn.com.zero.base.vo.ResponseVO;
import cn.com.zero.manager.auth.api.constant.TokenKeyConstant;
import cn.com.zero.manager.auth.constant.AuthConstant;
import cn.com.zero.manager.auth.enums.LoginClientEnum;
import cn.com.zero.manager.auth.mapper.IAuthMapper;
import cn.com.zero.manager.auth.po.LoginLogPO;
import cn.com.zero.manager.auth.po.ManagerPO;
import cn.com.zero.manager.auth.po.MenuPO;
import cn.com.zero.manager.auth.service.IAuthService;
import cn.com.zero.manager.auth.vo.OnlineVerificationTimeVO;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author guoxin
 * @version 1.0.0
 * @Description
 * @date 2023/2/13 15:10
 */
@Service
public class AuthService extends BaseService implements IAuthService {

    /**
     * 管理员mapper
     */
    private IAuthMapper authMapper;

    /**
     * token加密密钥
     */
    @Value(TokenKeyConstant.ENCODEKEY)
    private String encodeKey;

    /**
     * token解密密钥
     */
    @Value(TokenKeyConstant.DECODEKEY)
    private String decodeKey;

    /**
     * token有效时间
     */
    @Value(TokenKeyConstant.EXPIRETIME)
    private String expireTime;

    /**
     * token刷新次数上限
     */
    @Value(TokenKeyConstant.REFRESHTIMES)
    private String refreshTimes;

    /**
     * token签名加密算法
     */
    @Value(TokenKeyConstant.SIGNATUREALGORITHM)
    private String signatureAlgorithm;

    /**
     * token刷新时段
     */
    @Value(TokenKeyConstant.REFRESHINTERVAL)
    private String refreshInterval;

    /**
     * 对称加密密钥
     */
    @Value(TokenKeyConstant.SECUREKEY)
    private String secureUtilKey;

    /**
     * 消息操作service
     */
    private IMessageService messageService;

    /**
     * Redis工具类
     */
    private RedisUtil redisUtil;

    /**
     * 管理员登录
     *
     * @param username      用户名
     * @param password      用户密码
     * @param loginClient   登录设备
     * @return
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public ResponseVO login(String username, String password,Integer loginClient) {
        ManagerPO userPO = authMapper.selectUserByUserId(username);
        if (Objects.isNull(userPO)) {
            logger.error("用户名{}不存在", username);
            throw new ErrorCodeException(-114514L);
        }
        userStatus(userPO,username,loginClient);
        // 删除对应管理员的缓存
        redisUtil.delete(UserCacheKeyUtil.getMenuPermissionCacheKey(AuthConstant.MANAGER,
                LoginClientEnum.getValueByName(loginClient),userPO.getUserId()),
                UserCacheKeyUtil.getOnlineUserCacheKey(AuthConstant.MANAGER,
                        LoginClientEnum.getValueByName(loginClient),userPO.getUserId()));
        if (!SecureUtil.md5(password).equals(userPO.getPassword())) {
            // 登录时验证 密码错误需要 记录登录失败次数
            Integer count = authMapper.selectErrorCountByUserId(userPO.getUserId());
            if(count == null){
                authMapper.insertErrorLogin(userPO.getUserId());
            }else {
                count++;
                authMapper.updateErrorLogin(userPO.getUserId(),count);
                if(count >= AuthConstant.MAX_BADPASSWORD_COUNT){
                    authMapper.freezeUser(userPO.getUserId());
                }
            }
            String content = "用户密码错误";
            setLoginLog(userPO.getUserId(),content,AuthConstant.LOGINDEFEAT,loginClient);
            logger.error("用户{}密码错误", userPO.getUserId());
            ResponseVO responseVO = new ResponseVO();
            responseVO.setCode(-114515);
            responseVO.setMessage("用户密码错误");
            return responseVO;
        }
        ResponseVO responseVO = setLogAndToken(userPO,loginClient);
        return responseVO;
    }

    /**
     * 管理员退出
     *
     * @return
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public ResponseVO logout() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        String userId = request.getHeader(TokenKeyConstant.USERID);
        Integer loginClient = Integer.valueOf(request.getHeader(TokenKeyConstant.LOGINCLIENT));
        redisUtil.delete(UserCacheKeyUtil.getMenuPermissionCacheKey(AuthConstant.MANAGER,LoginClientEnum.getValueByName(loginClient),userId),
                UserCacheKeyUtil.getOnlineUserCacheKey(AuthConstant.MANAGER,LoginClientEnum.getValueByName(loginClient),userId));
        ResponseVO responseVO = new ResponseVO();
        responseVO.setMessage("注销成功");
        return responseVO;
    }

    /**
     * 打印登录日志
     *
     * @param userid        管理员Id
     * @param content       打印内容
     * @param status        打印状态
     * @param loginClient   登录设备
     */
    public void setLoginLog(String userid,String content,Integer status,Integer loginClient){
        String loginIP = getRealRequestIp();
        authMapper.insertLoginLog(content,status,loginIP,loginClient,userid);

    }

    /**
     * 用户手机登录
     *
     * @param mobile        手机号
     * @param smsCode       手机验证码
     * @param loginClient   登录设备
     * @return  token
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public ResponseVO smsLogin(String mobile, String smsCode, Integer loginClient) {
        String encrypt = SecureUtil.aes(secureUtilKey.getBytes()).encryptHex(mobile);
        ManagerPO userPO = authMapper.selectUserByUserMobile(encrypt);
        if(Objects.isNull(userPO)){
            logger.error("用户{}不存在", mobile);
            throw new ErrorCodeException(-114514L);
        }
        userStatus(userPO,mobile,loginClient);
        // 验证验证码是否正确
        Long code = messageService.checkVerificationCode(mobile, "code_login_user", smsCode);
        if(code < 0){
            ResponseVO responseVO = new ResponseVO();
            responseVO.setCode(code);
            responseVO.setMessage("验证码错误");
            return responseVO;
        }
        ResponseVO responseVO = setLogAndToken(userPO,loginClient);
        return responseVO;
    }

    /**
     * 发送手机验证码
     *
     * @param mobile    手机号
     */
    @Override
    public void sendLoginSmsCode(String mobile) {
        String ipAddress = getRealRequestIp();
        messageService.sendVerificationCode(mobile,"code_login_user",ipAddress);
    }

    /**
     * 判断用户状态
     *
     * @param userPO        管理员信息
     * @param user          管理员名称
     * @param loginClient   登录设备
     */
    private void userStatus(ManagerPO userPO,String user,Integer loginClient){
        if(userPO.getStatus() == AuthConstant.CANCEL){
            String content = "用户已注销";
            setLoginLog(userPO.getUserId(),content,AuthConstant.LOGINDEFEAT,loginClient);
            logger.error("该用户{}已注销", user);
            throw new ErrorCodeException(-114513L);
        }
        if(userPO.getStatus() == AuthConstant.FREEZE){
            String content = "用户已冻结";
            setLoginLog(userPO.getUserId(),content,AuthConstant.LOGINDEFEAT,loginClient);
            logger.error("该用户{}已冻结", user);
            throw new ErrorCodeException(-114512L);
        }
    }

    /**
     * 数据库登录日志打印、生成token、redis操作
     *
     * @param userPO        用户信息
     * @param loginClient   登录设备
     * @return
     */
    private ResponseVO setLogAndToken(ManagerPO userPO,Integer loginClient){
        // 获取用户权限信息
        List<MenuPO> roleCodes = authMapper.selectMenuByUserId(userPO.getUserId());
        List<MenuPO> menuPOList = roleCodes.stream().distinct().collect(Collectors.toList());
        // 将用户权限信息存入redis
        redisUtil.setObject(UserCacheKeyUtil.getMenuPermissionCacheKey(AuthConstant.MANAGER,
                LoginClientEnum.getValueByName(loginClient),userPO.getUserId()),
                JSONArray.toJSONString(menuPOList));
        //生成token并且设置到responseVO
        TokenUtil tokenUtil = new TokenUtil(setTokenProperties(userPO.getUserId()));
        Date date = new Date();
        String token = tokenUtil.generateToken(userPO.getUserId(), date);
        ResponseVO responseVO = new ResponseVO();
        responseVO.setMessage(token);
        responseVO.setData(menuPOList);
        // 删除用户登录失败日志
        authMapper.DeleteErrorLogin(userPO.getUserId());
        LoginLogPO loginLogPO = authMapper.selectLoginLog(userPO.getUserId());
        if(!Objects.isNull(loginLogPO)){
            authMapper.updateManager(loginLogPO.getCreateTime(),loginLogPO.getLoginIP(),userPO.getUserId());
        }
        //将用户记录到线上信息
        OnlineVerificationTimeVO onlineVerificationTimeVO = new OnlineVerificationTimeVO();
        onlineVerificationTimeVO.setUserID(userPO.getUserId());
        onlineVerificationTimeVO.setLoginIp(getRealRequestIp());
        onlineVerificationTimeVO.setLoginClient(loginClient);
        onlineVerificationTimeVO.setCreateTime(date);
        onlineVerificationTimeVO.setTokenCreateTime(date);
        redisUtil.setObjectAndTime(UserCacheKeyUtil.getOnlineUserCacheKey(AuthConstant.MANAGER,LoginClientEnum.getValueByName(loginClient),userPO.getUserId()),
                JSONObject.toJSONString(onlineVerificationTimeVO),Long.parseLong(expireTime), TimeUnit.SECONDS);
        String content = "登录成功";
        setLoginLog(userPO.getUserId(),content,AuthConstant.LOGINSUCCESS,loginClient);
        return responseVO;
    }

    /**
     * 创建TokenProperties对象
     *
     * @param userId    管理员Id
     * @return
     */
    private TokenProperties setTokenProperties(String userId){
        TokenProperties tokenProperties = new TokenProperties();
        tokenProperties.setIssuer(userId);
        tokenProperties.setEncodeKey(encodeKey);
        tokenProperties.setDecodeKey(decodeKey);
        tokenProperties.setExpireTime(Long.valueOf(expireTime));
        tokenProperties.setRefreshInterval(Long.valueOf(refreshInterval));
        tokenProperties.setRefreshTimes(Integer.valueOf(refreshTimes));
        tokenProperties.setSignatureAlgorithm(signatureAlgorithm);
        return tokenProperties;
    }

    /**
     * 获取IP地址
     *
     * @return
     */
    private String getRealRequestIp() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        if (null != ip && ip.contains(",")) {
            ip = ip.substring(0, ip.indexOf(","));
        }
        return ip;
    }

    @Autowired
    public void setAuthMapper(IAuthMapper authMapper) {
        this.authMapper = authMapper;
    }

    @Autowired
    public void setRedisUtil(RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
    }

    @Autowired(required = false)
    @DubboReference
    public void setMessageService(IMessageService messageService) {
        this.messageService = messageService;
    }
}

